﻿using System.Collections.Generic;
using System.Globalization;
using System.Xml.Linq;
using IcicleFramework.Entities;
using IcicleFramework.GameServices;
using Microsoft.Xna.Framework;

namespace IcicleFramework.Components.Projectile
{
    public delegate void ProjectileFiredHandler(IGameObject projectile);

    public class ProjectileComponent : BaseComponent, IProjectileComponent
    {

        #region Events

        public event ProjectileFiredHandler OnProjectileFired;

        #endregion


        #region Member Variables

        /// <summary>
        /// The amount of time that has passed since the last projectile was fired by our parent object.
        /// </summary>
        private float timeSinceLastFired;

        /// <summary>
        /// A list of active projectiles currently maintained by this ProjectileComponent.
        /// </summary>
        private List<IGameObject> activeProjectiles;

        /// <summary>
        /// The ProjectileManager we will use to request a new projectile.
        /// </summary>
        private ProjectileManager projectileManager;

        #endregion


        #region Properties
        
        /// <summary>
        /// Gets or sets the actual type of Projectile fired when requested (if possible).
        /// </summary>
        public IGameObject Projectile { get; set; }

        /// <summary>
        /// Checks whether or not another projectile may be fired.
        /// </summary>
        public bool AbleToFire
        {
            get
            {
                if (timeSinceLastFired > TimeBetweenShots)
                    return true;
                else
                    return false;
            }
        }

        /// <summary>
        /// Gets or sets the amount of time required between shots of a projectile.
        /// </summary>
        public float TimeBetweenShots { get; set; }

        /// <summary>
        /// The maximum number of projectiles that may be spawned by this ProjectileComponent at one time.
        /// </summary>
        public int MaxProjectiles { get; set; }

        /// <summary>
        /// Gets the number of projectiles spawned by this ProjectileComponent that are currently active.
        /// </summary>
        public int CurrProjectiles
        {
            get { return activeProjectiles.Count; }
        }

        /// <summary>
        /// Gets or sets the offset projectiles are spawned at, relative to this ProjectileComponents parent.
        /// </summary>
        public Vector2 Offset { get; set; }

        /// <summary>
        /// Gets or sets the angle at which projectiles are fired at.
        /// </summary>
        public float Angle { get; set; }

        /// <summary>
        /// Gets the game world location where projectiles are spawned from.
        /// </summary>
        public Vector2 FiringLocation
        {
            get { return Parent.Position + Offset; }
        }
        
        #endregion


        #region Constructors

        /// <summary>
        /// Creates a new ProjectileComponent with default values and no projectile.
        /// </summary>
        public ProjectileComponent()
        {
            Projectile = null;
            TimeBetweenShots = 1.0f;
            MaxProjectiles = 10;
            Offset = Vector2.Zero;
            timeSinceLastFired = 0.0f;
            Parent = null;

            activeProjectiles = new List<IGameObject>();
        }
        
        public ProjectileComponent(ProjectileComponent old)
            : base(old)
        {
            this.Projectile = old.Projectile.DeepClone();
            this.TimeBetweenShots = old.TimeBetweenShots;
            this.MaxProjectiles = old.MaxProjectiles;
            this.Offset = old.Offset;

            this.timeSinceLastFired = 0.0f;

            activeProjectiles = new List<IGameObject>();
        }

        #endregion



        public override void Update(GameTime gameTime)
        {
            timeSinceLastFired += (float) gameTime.ElapsedGameTime.TotalSeconds;
        }

        /// <summary>
        /// Requests that Projectile(s) be fired from this ProjectileComponent.
        /// </summary>
        public virtual void Fire()
        {
            if (AbleToFire)
            {
                timeSinceLastFired = 0.0f;

                //Ask the ProjectileManager to give us some projectiles...
                IGameObject projectile = projectileManager.CreateProjectile(this, this.Parent.Position, this.Angle);

                //If we got a projectile back then launch the fire
                if (projectile != null)
                {
                    OnProjectileFired(projectile);
                }
            }
        }

        public override IBaseComponent DeepClone()
        {
            ProjectileComponent newComponent = new ProjectileComponent();

            return newComponent;
        }

        public void RegisterProjectileManager(ProjectileManager manager)
        {
            if (manager != null)
                this.projectileManager = manager;
        }

        public override void Deserialize(XElement element)
        {
            if (element.Element("timeBetweenShots") != null)
            {
                this.TimeBetweenShots = float.Parse(element.Element("timeBetweenShots").Value, NumberStyles.Float,
                                                    CultureInfo.InvariantCulture);
            }

            if (element.Element("maxShots") != null)
            {
                this.MaxProjectiles = int.Parse(element.Element("maxShots").Value, NumberStyles.Integer,
                                                CultureInfo.InvariantCulture);
            }

            if (element.Element("offset") != null)
            {
                int offsetX = int.Parse(element.Element("offset").Attribute("x").Value, NumberStyles.Integer,
                                        CultureInfo.InvariantCulture);
                int offsetY = int.Parse(element.Element("offset").Attribute("y").Value, NumberStyles.Integer,
                                        CultureInfo.InvariantCulture);

                this.Offset = new Vector2(offsetX, offsetY);
            }

            //Now we need to build the actual projectile itself...
            if (element.Element("projectile") != null && element.Element("projectile").Attribute("type") != null)
            {
                IGameObjectFactory factory = GameServiceManager.GetService<IGameObjectFactory>();

                this.Projectile = factory.GenerateGameObjectFromName(element.Element("projectile").Attribute("type").Value);
            }
        }

        public override void Dispose()
        {
            OnProjectileFired = null;

            base.Dispose();
        }
    }
}
